package com.arpa.wms.cancel.delivery;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.arpa.ntocc.common.common.exception.ServiceException;
import com.arpa.ntocc.common.common.util.UserUtil;
import com.arpa.wms.domain.dto.InventoryOperationDto;
import com.arpa.wms.domain.entity.*;
import com.arpa.wms.domain.enums.InventoryRecordTypeEnum;
import com.arpa.wms.domain.enums.TempLocationTypeEnum;
import com.arpa.wms.service.*;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.Data;
import lombok.extern.log4j.Log4j2;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 订单取消处理
 * @author leo
 */
@Log4j2
public abstract class AbstractDeliveryCancelHandler {


    @Resource
    protected IInventoryService inventoryService;

    @Resource
    protected   IInventoryRecordService inventoryRecordService;

    @Resource
    protected   InventoryOperationService inventoryOperationService;

    @Resource
    protected IDeliveryDistributionService deliveryDistributionService;

    @Resource
    protected  ILocationService locationService;
    /**
     * 订单取消处理
     * @param delivery 订单
     * @return 处理结果
     */
    abstract public boolean handle(IDeliveryService deliveryService, Delivery delivery);




    /**
     * 库存处理
     * 分配库存退回原库位；
     *
     * @param delivery 待取消订单信息
     * @param needReturnSourceInventory 是否退回原库位
     * @author liuxr
     * @date 2021/6/2 13:32
     */
    public void handleInventory(Delivery delivery,boolean needReturnSourceInventory) {
        String deliveryCode = delivery.getCode();
        String groupCode = delivery.getGroupCode();
        //setp1:根据订单号获取所有分配明细
        List<DeliveryDistribution> deliveryDistributions = deliveryDistributionService.list(Wrappers.lambdaQuery(DeliveryDistribution.class)
                .select(DeliveryDistribution::getCode,
                        DeliveryDistribution::getDeliveryCode,
                        DeliveryDistribution::getInventoryCode,
                        DeliveryDistribution::getOldInventoryCode,
                        DeliveryDistribution::getDistributionQuantity,
                        DeliveryDistribution::getPickingQuantity,
                        DeliveryDistribution::getPickStockOutNum)
                .eq(DeliveryDistribution::getDeliveryCode, deliveryCode)
                .eq(DeliveryDistribution::getGroupCode, groupCode));

        //根据库存分配记录获取临时库存和原始库存信息
        Map<String, Inventory> inventoryMap = inventoryInfo(deliveryDistributions,needReturnSourceInventory );

        //获取订单要减少的临时库存、要增加的原始库存、操作数量
        OperationData cancelQuantity  = getCancelQuantity(delivery, deliveryDistributions, inventoryMap,needReturnSourceInventory);

        operationInventory(deliveryCode,cancelQuantity,inventoryMap);
    }


    /**
     * 根据库存分配记录获取临时库存和原始库存信息
     * @author liuxr
     * @date 2021/11/19 16:52
     * @param deliveryDistributions 订单分配记录
     * @param needReturnSourceInventory 是否退回原库位
     * @return java.util.Map<java.lang.String,com.arpa.wms.gateway.domain.Inventory> 库存信息 key：库存标识，value：库存信息
     */
    private Map<String, Inventory> inventoryInfo(List<DeliveryDistribution> deliveryDistributions ,boolean needReturnSourceInventory ){
        //通过分配明细获取所有临时库存标识
        Set<String> inventoryCodeSet = deliveryDistributions.stream()
                .map(DeliveryDistribution::getInventoryCode).collect(Collectors.toSet());

        if(needReturnSourceInventory){
            //通过分配明细获取所有原始库存标识
            Set<String> oldInventoryCodeSet = deliveryDistributions.stream()
                    .map(DeliveryDistribution::getOldInventoryCode)
                    .collect(Collectors.toSet());
            //合并所有的库存标识
            inventoryCodeSet.addAll(oldInventoryCodeSet);
        }


        //通过临时库存、原始库存获取所有库存信息
        List<Inventory> inventories = inventoryService.list(Wrappers.lambdaQuery(Inventory.class)
                .in(Inventory::getCode, inventoryCodeSet));
        //将库存信息通过库存code转成map
        return inventories.stream().collect(Collectors.toMap(Inventory::getCode, r -> r));
    }


    /**
     * 构造要处理的数据
     * 订单要减少的临时库存、要增加的原始库存、操作数量
     *
     * @param delivery 订单
     * @param deliveryDistributions 订单分配明细
     * @param inventoryMap 库存信息
     * @param needReturnSourceInventory 临时库存是否退回原库位，true：退回原库位，false：退回退拣库位
     * @return java.util.Map<java.lang.String, java.lang.Object>
     * @author liuxr
     * @date 2021/6/2 13:38
     */
    private OperationData getCancelQuantity(Delivery delivery,
                                            List<DeliveryDistribution> deliveryDistributions,
                                            Map<String, Inventory> inventoryMap,
                                            boolean needReturnSourceInventory) {
        String deliveryCode = delivery.getCode();
        String warehouseCode = delivery.getWarehouseCode();
        //获取订单要减少的临时库存、要增加的原始库存、操作数量
        OperationData operationData = new OperationData();
        //将分配明细按照临时库存标识分组，计算临时库存需要减少的数量
        //返回 Map<临时库存标识,合计分配数量> 要减少库存的临时库存及数量
        List<InventoryOperationDto> subtract = deliveryDistributions.stream()
                .collect(Collectors.groupingBy(DeliveryDistribution::getInventoryCode))
                .entrySet().stream()
                .map(m -> {
                    InventoryOperationDto dto = new InventoryOperationDto();
                    dto.setUuid(IdUtil.simpleUUID());
                    dto.setInventoryCode(m.getKey());
                    dto.setQuantity(m.getValue()
                            .stream()
                            .map(DeliveryDistribution::getDistributionQuantity)
                            .reduce(BigDecimal.ZERO, BigDecimal::add));
                    dto.setInventory(inventoryMap.get(m.getKey()));
                    return dto;
                })
                .collect(Collectors.toList());
        //所有库存操作记录集合
        List<InventoryRecord> inventoryRecords = new ArrayList<>();
        String groupCode = UserUtil.getBranchCode();

        //创建减库存操作记录
        subtract.forEach(r -> {
            InventoryRecord record = new InventoryRecord();
            BeanUtil.copyProperties(r.getInventory(), record);
            record.setCode(IdUtil.simpleUUID());
            record.setGroupCode(groupCode);
            record.setInventoryCode(r.getInventoryCode());
            record.setSourceCode(deliveryCode);
            record.setNum(r.getQuantity().negate());
            record.setType(InventoryRecordTypeEnum.DELIVERY.getValue());
            record.setRemarks("订单："+deliveryCode+"取消，分配的临时库存退还原库位");
            inventoryRecords.add(record);
        });
        // 接收处理后的数据
        List<InventoryOperationDto> subtractData = getInventoryOperationDtos(subtract);
        operationData.setSubtractData(subtractData);


        if(needReturnSourceInventory) {
            log.info("退回原库存处理");
            //将分配明细按照原始库存标识分组，计算原始库存需要增加的数量
            //返回 Map<原始库存标识,合计分配数量> 要增加库存的原始库存及数量，同时用于拣货单明细更新取消数量用
            List<InventoryOperationDto> add = deliveryDistributions.stream()
                    .collect(Collectors.groupingBy(DeliveryDistribution::getOldInventoryCode))
                    .entrySet().parallelStream()
                    .map(m -> {
                        InventoryOperationDto dto = new InventoryOperationDto();
                        dto.setUuid(IdUtil.simpleUUID());
                        dto.setInventoryCode(m.getKey());
                        dto.setQuantity(m.getValue()
                                .stream()
                                .map(DeliveryDistribution::getDistributionQuantity)
                                .reduce(BigDecimal.ZERO, BigDecimal::add));
                        dto.setInventory(inventoryMap.get(m.getKey()));
                        return dto;
                    })
                    .collect(Collectors.toList());

            //创建加库存操作记录
            add.forEach(r -> {
                InventoryRecord record = new InventoryRecord();
                BeanUtil.copyProperties(r.getInventory(), record);
                record.setCode(IdUtil.simpleUUID());
                record.setGroupCode(groupCode);
                record.setInventoryCode(r.getInventoryCode());
                record.setSourceCode(deliveryCode);
                record.setNum(r.getQuantity());
                record.setType(InventoryRecordTypeEnum.DELIVERY.getValue());
                record.setRemarks("订单："+deliveryCode+"取消，分配的临时库存退还原库位");
                inventoryRecords.add(record);
            });
            // 接收处理后的数据
            List<InventoryOperationDto> addData = getInventoryOperationDtos(add);
            operationData.setAddData(addData);
        }else{
            log.info("退回退拣库位处理");
            // 获取退减临时库位
            Location reLocation = locationService.getOne(Wrappers.lambdaQuery(Location.class)
                    .select(Location::getCode)
                    .eq(Location::getWarehouseCode, warehouseCode)
                    .eq(Location::getSerialNumber, TempLocationTypeEnum.RE));
            if (null == reLocation) {
                log.error("仓库【{}】缺少退减库位", warehouseCode);
                throw new ServiceException("拣货确认失败，仓库缺少退减库位");
            }

            //退减库存
            List<Inventory> reInventorys = new ArrayList<>();
            //创建减库存操作记录
            subtract.forEach(r -> {
                //退减库存信息
                Inventory inventory = new Inventory();
                BeanUtil.copyProperties(r.getInventory(), inventory);
                inventory.setCode(IdUtil.simpleUUID());
                inventory.setUseQuantity(BigDecimal.ZERO);
                inventory.setOldInventoryCode(r.getInventory().getCode());
                inventory.setTempCode(StrUtil.EMPTY);
                inventory.setTempItemCode(StrUtil.EMPTY);
                inventory.setTempRegisterCode(StrUtil.EMPTY);
                inventory.setLocation(reLocation.getCode());
                inventory.setQuantity(r.getQuantity());
                // 抹去容器号等信息
                inventory.setContainerBarCode(StrUtil.EMPTY);
                inventory.setRemarks("订单："+deliveryCode+"取消，已拣商品存放到退拣库位");
                reInventorys.add(inventory);

                //退减库存擦插入操作记录
                InventoryRecord reRecord = new InventoryRecord();
                BeanUtil.copyProperties(inventory, reRecord);
                reRecord.setCode(IdUtil.simpleUUID());
                reRecord.setGroupCode(groupCode);
                reRecord.setInventoryCode(inventory.getCode());
                reRecord.setSourceCode(deliveryCode);
                reRecord.setNum(inventory.getQuantity());
                reRecord.setType(InventoryRecordTypeEnum.OUTBOUND.getValue());
                reRecord.setRemarks("订单："+deliveryCode+"取消，已拣商品存放到退拣库位");
                inventoryRecords.add(reRecord);
            });


            List<Inventory> reInventoryDatas = new ArrayList<>();
            // 数据分组统计处理
            reInventorys.stream()
                    .collect(Collectors.groupingBy(Inventory::getOldInventoryCode, Collectors.toList()))
                    .forEach((inventoryCode, dtos) -> {
                        dtos.stream()
                                .reduce((a, b) -> {
                                    Inventory inventory = new Inventory();
                                    BeanUtil.copyProperties(a, inventory);
                                    inventory.setCode(IdUtil.simpleUUID());
                                    inventory.setQuantity(a.getQuantity().add(b.getQuantity()));
                                    return inventory;
                                })
                                .ifPresent(reInventoryDatas::add);
                    });


            operationData.setReInventoryData(reInventoryDatas);
        }


        operationData.setInventoryRecords(inventoryRecords);
        return operationData;
    }



    /**
     * 更新库存
     *
     * @param deliveryCode  订单号
     * @param data         操作数据
     * @param inventoryMap 原库存集合
     * @author liuxr
     * @date 2021/6/2 16:08
     */
    private void operationInventory(String deliveryCode,OperationData data, Map<String, Inventory> inventoryMap) {

        //要减少的临时库存
        List<InventoryOperationDto> subtractData = data.getSubtractData();
        //要增加的原始库存
        List<InventoryOperationDto> addData = data.getAddData();
        //库存操作记录集合
        List<InventoryRecord> inventoryRecords =data.getInventoryRecords();
        //要增加的退拣库存
        List<Inventory> reInventoryData = data.getReInventoryData();

        //减少临时库存
        inventoryOperationService.subtractBatch(subtractData);

        //减少临时库存
        log.info("订单取消处理，开始处理订单【{}】减少临时库存【{}】",deliveryCode, JSONUtil.toJsonStr(subtractData));
        if(IterUtil.isNotEmpty(subtractData)) {
            inventoryOperationService.subtractBatch(subtractData);
            validated(deliveryCode, subtractData, inventoryMap);
        }

        //批量加库存
        log.info("订单取消处理，开始处理订单【{}】批量加原库存【{}】", deliveryCode, JSONUtil.toJsonStr(addData));
        if(IterUtil.isNotEmpty(addData)) {
            inventoryOperationService.addBatch(addData);
        }

        log.info("订单取消处理，开始处理订单【{}】增加退拣库存【{}】", deliveryCode, JSONUtil.toJsonStr(reInventoryData));
        if(IterUtil.isNotEmpty(reInventoryData)) {
            inventoryService.saveBatch(reInventoryData);
        }

        //批量插入操作记录
        log.info("订单取消处理，开始处理订单【{}】批量插入操作记录【{}】",deliveryCode,JSONUtil.toJsonStr(inventoryRecords));
        if(IterUtil.isNotEmpty(inventoryRecords)) {
            inventoryRecordService.saveBatch(inventoryRecords);
        }

    }

    /**
     * 临时库存操作结果校验
     * @author liuxr
     * @date 2021/11/23 11:08
     * @param deliveryCode 订单号
     * @param subtractData 操作数据
     * @param inventoryMap 源数据
     */
    private void validated(String deliveryCode,List<InventoryOperationDto> subtractData, Map<String, Inventory> inventoryMap){
        log.info("订单取消处理，临时库存操作结果校验,订单号【{}】",deliveryCode);
        //进行库存验证
        //库存操作信息通过库存code转成map
        Map<String, InventoryOperationDto> stringInventoryOperationDtoMap = subtractData.stream().collect(Collectors.toMap(InventoryOperationDto::getInventoryCode, r -> r));

        //更新后的临时库存
        Set<String> inventoryCodes = subtractData.stream().map(InventoryOperationDto::getInventoryCode).collect(Collectors.toSet());
        List<Inventory> newInventorys = inventoryService.list(Wrappers.lambdaQuery(Inventory.class)
                .select(Inventory::getCode, Inventory::getQuantity)
                .in(Inventory::getCode, inventoryCodes));
        newInventorys.forEach(n -> {
            String inventoryCode = n.getCode();
            //更新前临时库存
            Inventory oldInventory = inventoryMap.get(inventoryCode);
            //要操作的库存数据
            InventoryOperationDto inventoryOperationDto = stringInventoryOperationDtoMap.get(inventoryCode);
            if (oldInventory.getQuantity().compareTo(n.getQuantity().add(inventoryOperationDto.getQuantity())) != 0) {
                log.info("订单取消处理异常，订单【{}】临时库存【{}】扣减失败",deliveryCode,inventoryCode);
                throw new ServiceException("订单取消处理异常，订单【"+deliveryCode+"】临时库存【" + inventoryCode + "】扣减失败");
            }
        });
    }


    private List<InventoryOperationDto> getInventoryOperationDtos(List<InventoryOperationDto> data) {
        List<InventoryOperationDto> result = new ArrayList<>();
        // 数据分组统计处理
        data.stream()
                .collect(Collectors.groupingBy(InventoryOperationDto::getInventoryCode, Collectors.toList()))
                .forEach((id, transfer) -> {
                    transfer.stream()
                            .reduce((a, b) ->

                            {
                                InventoryOperationDto dto = new InventoryOperationDto();
                                dto.setUuid(IdUtil.simpleUUID());
                                dto.setInventoryCode(a.getInventoryCode());
                                dto.setQuantity(a.getQuantity().add(b.getQuantity()));
                                dto.setInventory(a.getInventory());
                                return dto;
                            })
                            .ifPresent(result::add);
                });
        return result;
    }



    @Data
    class OperationData{
        List<InventoryOperationDto> subtractData ;
        //要增加的原始库存
        List<InventoryOperationDto> addData ;

        List<InventoryRecord> inventoryRecords;

        List<Inventory> reInventoryData;
    }
}
